package com.fowo.api.sys.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fowo.api.common.model.RException;
import com.fowo.api.sys.entity.SysSerial;
import com.fowo.api.sys.mapper.SysSerialMapper;
import com.fowo.api.sys.model.NewSerialRequest;
import com.fowo.api.sys.service.SysSerialService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SysSerialServiceImpl extends ServiceImpl<SysSerialMapper, SysSerial> implements SysSerialService {
    @Resource
    private SysSerialServiceImpl sysSerialService;

    @Transactional(rollbackFor = Exception.class)
    public LinkedList<String> getNewSerial(String group, String objectName) {
        final SysSerial sysSerial = baseMapper.selectOne(Wrappers.<SysSerial>query()
                .eq(StringUtils.hasText(group), "group", group)
                .eq(!StringUtils.hasText(group), "object_name", objectName)
        );
        if (sysSerial == null) {
            throw new RException("未找到此配置的序列号");
        }
        Date date = new Date();
        String currentTime = StringUtils.hasText(sysSerial.getTimeFormat()) ? new SimpleDateFormat(sysSerial.getTimeFormat()).format(date) : "";
        Long serial = currentTime.equals(sysSerial.getLastTime()) ? sysSerial.getSerial() + 1 : 1;
        String serialString = String.format("%0" + sysSerial.getNumberLength() + "d", serial);
        LinkedList<String> list = CollUtil.newLinkedList(sysSerial.getPrefix(), currentTime, serialString, sysSerial.getSuffix());
        SysSerial updateSysSerial = new SysSerial();
        updateSysSerial.setId(sysSerial.getId());
        updateSysSerial.setSerial(serial);
        updateSysSerial.setLastTime(currentTime);
        updateById(updateSysSerial);
        return list;
    }

    private List<LinkedList<String>> getNewSerials(String group, String objectName, int count) {
        if (count <= 0) {
            return Collections.emptyList();
        }
        for(int retry = 0; retry < 5; retry++) {
            final SysSerial sysSerial = baseMapper.selectOne(Wrappers.<SysSerial>query()
                    .eq(StringUtils.hasText(group), "group", group)
                    .eq(!StringUtils.hasText(group), "object_name", objectName)
            );
            if (sysSerial == null) {
                throw new RException("未找到此配置的序列号");
            }
            Date date = new Date();
            String currentTime = StringUtils.hasText(sysSerial.getTimeFormat()) ? new SimpleDateFormat(sysSerial.getTimeFormat()).format(date) : "";
            Long serial = currentTime.equals(sysSerial.getLastTime()) ? sysSerial.getSerial() : 0;
            Long rawSerial = sysSerial.getSerial();
            List<LinkedList<String>> result = new ArrayList<>();
            for (int i = 0; i < count; i++) {
                serial++;
                String serialString = String.format("%0" + sysSerial.getNumberLength() + "d", serial);
                LinkedList<String> list = CollUtil.newLinkedList(sysSerial.getPrefix(), currentTime, serialString, sysSerial.getSuffix());
                result.add(list);
            }
            boolean updated = new UpdateChainWrapper<SysSerial>(baseMapper)
                    .set("serial", serial)
                    .set("last_time", currentTime)
                    .eq("id", sysSerial.getId())
                    .eq("serial", rawSerial)
                    .update();
            if (!updated) {
                // 同步冲突
                continue;
            }
            return result;
        }
        throw new RException("getNewSerials retry 5 times can not get new serials for Synchronization conflicts.");
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public String getNewSerial(NewSerialRequest request) {
        List<String> newSerial = sysSerialService.getNewSerial(request.getGroup(), request.getObjectName());
        return CollUtil.join(newSerial, StrUtil.EMPTY);
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    @Override
    public List<String> getNewSerials(NewSerialRequest request) {
        List<LinkedList<String>> newSerials = getNewSerials(request.getGroup(), request.getObjectName(), request.getCount());
        return newSerials.stream().map(l -> CollUtil.join(l, StrUtil.EMPTY)).collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String getCustomNewSerial(NewSerialRequest request, Map<Integer, String> map) {
        List<String> newSerial = sysSerialService.getNewSerial(request.getGroup(), request.getObjectName());
        if (MapUtil.isEmpty(map)) {
            return CollUtil.join(newSerial, StrUtil.EMPTY);
        }
        newSerial = CollUtil.removeBlank(newSerial);
        for (Map.Entry<Integer, String> item : map.entrySet()) {
            int key = Convert.toInt(item.getKey());
            if (CompareUtil.compare(key, newSerial.size()) < 0) {
                newSerial.add(key, Convert.toStr(item.getValue(), StrUtil.EMPTY));
            } else {
                newSerial.add(Convert.toStr(item.getValue(), StrUtil.EMPTY));
            }
        }
        return CollUtil.join(newSerial, StrUtil.EMPTY);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String getNewSerialParams(String objectName) {
        NewSerialRequest request = new NewSerialRequest(null, objectName, 1);
        return getNewSerial(request);
    }
}
